
\documentclass[a4paper]{article}

\input{style/ch_xelatex.tex}


\lstset{frame=, basicstyle={\footnotesize\ttfamily}}



\graphicspath{ {images/} }
\usepackage{ctex}



\usepackage[square,numbers]{natbib}
\bibliographystyle{abbrvnat}


\begin{document}
\renewcommand{\contentsname}{目\ 录}
\renewcommand{\appendixname}{附录}
\renewcommand{\appendixpagename}{附录}
\renewcommand{\refname}{参考文献}

\renewcommand{\tablename}{表}
\renewcommand{\today}{\number\year 年 \number\month 月 \number\day 日}

\title{{\Huge 可视语言与信息可视化{\large\linebreak\\}}{\Large 团队ID: 12\linebreak}
{\Large  \linebreak\linebreak}}
\author{ \\
\\\\\\
天津大学,智能与计算学部}
\date{\today}
\maketitle
\newpage

\begin{center}
\tableofcontents\label{c}
\end{center}
\newpage


\begin{center}
{\Large\bf{摘\ 要\\}}

大作业要求大家按照论文短文的格式进行书写，参考文献~\cite{bayrak2020pragma, govyadinov2019graph}。



\end{center}

\newpage



\section{引言}
\label{overview}

随着论文数量快速增长，阅读论文变得极其耗时，我们期望能快速对文档集获得宏观整体的认知。本项目通过将论文集可视化为散点图，添加可视化交互，使得用户可以快速感知文章主题在空间内的分布，以及某个区域中文献的关键词，同时也可以让用户选择只对某一时间段内的文献进行查看。

\newpage

\section{相关工作}

%调研相关论文发表，搜索CNKI或者Google scholar 等学术引擎，了解该领域研究现状。参考文献格式为~\cite{bayrak2020pragma}。
调研了相关需求功能的实现技术，根据数据特征以及实际使用情景进行具体技术实现。

\newpage

\section{发现与主题演化}
\begin{enumerate}
    \item
    在可视化顶级会议VIS和VR历史上的文章中，大部分文章都更偏向于集中阐述一个主题，跨两个主题的文章数量就已经相对较少了，跨三个主题的文章就更加少之又少。
    作为最为火热的主题topic4，有众多文章都与topic4相关度大于0.3，但尽管如此，与topic4相关的交叉主题文章仍然是少之又少，例如图~\ref{fig:topic4_topic5}和图~\ref{fig:topic4_topic6}分别是跨topic4、topic5和跨topic4、topic6的文章，可见其数量很少。
    
    若选择不像topic4那么火热的主题作为主要主题观察其与其他主题的交叉情况，例如topic2，结果同样是少之又少。其中topic1、topic5都与topic2没有交叉文章，如图~\ref{fig:topic2_topic3}~是topic2和topic3的交叉情况，仅有三个。如图~\ref{fig:topic1_topic4}~是topic1和topic4的交叉情况，这则是跨主题文章中数量最多的一类。
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/topic4_topic5.png}
        \caption{topic4\_topic5}\label{fig:topic4_topic5}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/topic4_topic6.png}
        \caption{topic4\_topic6}\label{fig:topic4_topic6}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/topic2_topic3.png}
        \caption{topic2\_topic3}\label{fig:topic2_topic3}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}

    \item
    在可视化顶级会议VIS和VR历史上的文章中，文章的关键词与文章的主题分布具有一定的关联。进一步观察可以发现，根据不同的主题，关键词会有少部分相同，也会有许多不同之处，但各主题基本都有涉及的关键词是visualization，图~\ref{fig:keywords1}到图~\ref{fig:keywords8}展示了8个topic范围内的大部分关键词，词频由上至下由左至右依次递减。
    再从时间线上来看，上世纪90年代初期的文章，关键词更多的是侧重于visualization、data、device、image；几年后，关键词开始向algorithm、volume、method倾斜，由此推测算法方向的数据处理可视化开始兴起；再往后，当时间来到21世纪，rendering、surface等关键词开始兴起，关于立体渲染等相关的内容开始流行；接下来是imagedisplay等关键词占据上风，algorithm开始有所减少；
    时间来到2010年左右，interactive开始频繁的出现，可视化当中，交互开始越发被重视；时至今日，visualization和data也仍是关键的话题。
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/keywords1.png}
        \caption{keywords for topic1}\label{fig:keywords1}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/keywords2.png}
        \caption{keywords for topic2}\label{fig:keywords2}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/keywords3.png}
        \caption{keywords for topic3}\label{fig:keywords3}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/keywords4.png}
        \caption{keywords for topic4}\label{fig:keywords4}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/keywords5.png}
        \caption{keywords for topic5}\label{fig:keywords5}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/keywords6.png}
        \caption{keywords for topic6}\label{fig:keywords6}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/keywords7.png}
        \caption{keywords for topic7}\label{fig:keywords7}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/keywords8.png}
        \caption{keywords for topic8}\label{fig:keywords8}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}

    \item 
    在可视化顶级会议VIS和VR上的文章数量从20世纪90年代以来快速增长，所涵盖的话题以及关键词越来越丰富，随着科技的高速发展也带动着可视化领域的研究的快速进步。

\end{enumerate}

\newpage

\section{各主题在空间中的分布}

如图~\ref{fig:overall}所示，我们为主题在空间中的分布设计了这样的交互和可视化：用户可以点击右上角8个主题中的任意一个或多个，以表示选中一个主题或多个主题，选中主题后散点图中与该主题相关度大于0.3的点会变为黑色并有先放大再变为原大小的动画效果，以此来达到醒目效果。若只选中一个主题，则高亮的点仅是与对应主题相关度大于0.3的点，若选中的多个主题，则高亮的点则是具有交叉主题的点。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.6\textwidth]{images/overall.png}
    \caption{overall}\label{fig:overall}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

接下来，我们一一展示各个主题在空间中的位置分布。

\begin{enumerate}
\item
如图~\ref{fig:topic1}所示，与topic1相关度大于0.3的点主要分布在文档空间的上部居中的位置。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.6\textwidth]{images/topic1.png}
    \caption{topic1}\label{fig:topic1}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

\item
如图~\ref{fig:topic2}所示，与topic2相关度大于0.3的点主要分布在文档空间的左下角，还有少部分文章分布在右下角。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.6\textwidth]{images/topic2.png}
    \caption{topic2}\label{fig:topic2}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

\item
如图~\ref{fig:topic3}所示，与topic3相关度大于0.3的点主要分布在文档空间的左侧略微靠下的位置。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.6\textwidth]{images/topic3.png}
    \caption{topic3}\label{fig:topic3}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

\item
如图~\ref{fig:topic4}所示，与topic4相关度大于0.3的点占据了文档空间的右侧几乎全部区域，与其相关的文章数量较多。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.6\textwidth]{images/topic4.png}
    \caption{topic4}\label{fig:topic4}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

\item
如图~\ref{fig:topic5}所示，与topic5相关度大于0.3的点主要分布在文档空间的左侧略微靠上的位置。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.6\textwidth]{images/topic5.png}
    \caption{topic5}\label{fig:topic5}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

\item
如图~\ref{fig:topic6}所示，与topic6相关度大于0.3的点主要分布在文档空间的左上角位置。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.6\textwidth]{images/topic6.png}
    \caption{topic6}\label{fig:topic6}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

\item
如图~\ref{fig:topic7}所示，与topic7相关度大于0.3的点主要分布在文档空间的下部，较为神奇的是还有一个点位于空间的上部，与大多数点分隔空间的两侧。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.6\textwidth]{images/topic7.png}
    \caption{topic7}\label{fig:topic7}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

\item
如图~\ref{fig:topic8}所示，与topic8相关度大于0.3的点主要分布在文档空间的中部位置。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=0.6\textwidth]{images/topic8.png}
    \caption{topic8}\label{fig:topic8}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}
\end{enumerate}

\newpage

\section{交叉主题}

在我们的主题分布交互中，选中某个主题后，该主题字样会显示为黑色，选中多个主题则会将对多个主题的相关度都大于0.3的文章高亮显示。

\begin{enumerate}
    \item 
    选中topic1和topic2后，发现没有文章同时对topic1和topic2的相关度都大于0.3。这也可以理解，在上一部分介绍中提到，但选中topic1时，散点分布在空间上部，但选中topic2时，散点分布在空间下部，自然不会有交叉部分。

    \item
    上一部分提到与topic4相关的文章数量较多，经过可视化可以发现，交叉topic4和topic8主题的文章主要分布在中部靠右下位置，且较为集中,散点图如图%~\ref{fig:topic4_topic8}所示。
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/topic4AndTopic8.png}
        \caption{topic4\_topic8}\label{fig:topic4_topic8}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}

    \item
    如图~\ref{fig:topic1_topic4}所示，交叉topic1和topic4主题的文章主要分布在空间的上部靠右位置，且是跨主题文章中数量相对较多的一类。
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.6\textwidth]{images/topic1_topic4.png}
        \caption{topic1\_topic4}\label{fig:topic1_topic4}
        \vspace{\baselineskip} % 表示图与正文空一行
    \end{figure}

\end{enumerate}

\newpage

\section{问题描述和解决方案}
\label{Data and Task Abstraction}

\begin{itemize}
    \item 
    在完成透镜功能时，首先进行需求分析。所要求实现的透镜要能够移动、能够缩放大小，透镜选择范围内的数据要进行区分，同时也要显示选择范围内的论文的关键词，还要处理好这部分交互与其他部分交互的冲突。
    因此分为实现透镜的绘制与变化、透镜位置与数据选择、关键词显示绘制三部分来完成。
    
    透镜的绘制通过click的监听来调去，只有当用户鼠标点击时，才会绘制透镜，保持良好的用户体验。对于此处的点击与绘制笔刷进行年份选择时还有选择topic的冲突，通过增加设置了flag以及位置范围进行了规避。
    透镜的位置通过mouse和zoom来实现，数据选择通过比较数据点和当前鼠标位置的关系完成。关键词的显示绘制重要在于使用map进行关键词的统计，返回将数据按词频进行整理。
\end{itemize}

\newpage


\section{实验结果和案例分析}\label{sub:ptxeva}
    图~\ref{fig:result1}展示了选中特定年份范围后使用透镜进行区域选择显示关键词的情况。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=1.0\textwidth]{images/result1.png}
    \caption{实验结果1}\label{fig:result1}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

    图~\ref{fig:result2}展示了选中特定年份范围后展示topic4的文章的空间分布的情况。
\begin{figure}[htbp]
    \centering
    \includegraphics[width=1.0\textwidth]{images/result2.png}
    \caption{实验结果2}\label{fig:result2}
    \vspace{\baselineskip} % 表示图与正文空一行
\end{figure}

\newpage


\section{组员分工}

本小组成员为郭以勒，闫瑞泽，范哲昊，具体分工如下：

\begin{itemize}
    \item 郭以勒负责实现时间过滤器的可视化交互，让用户可以选择查看一段时间内的论文。
    \item 闫瑞泽负责实现论文散点图的绘制、让用户快速感知文章主题在空间内的分布以及快速找到跨主题文章的可视化交互。
    \item 范哲昊负责实现一个交互透镜，透镜大小可调，可跟随用户鼠标区域框选文章并显示区域内文章关键词。
\end{itemize}

\newpage


\section{总结}

\newpage


\section{工程代码}

\begin{lstlisting}
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>scatter</title>
            <script src="https://d3js.org/d3.v4.js"></script>
            <!-- <script type="text/javascript" 
            src="../data/data.js"></script> -->
            <style>
                body {
                    width: 100%;
                    display: flex;
                    margin: 0;
                }
                
                body .left{
                    width: 28vw;
                    height: 100%;
                    padding-bottom: 20vh;
                    overflow: auto;
                    background-color: #69b3a2;
                }
                
                body .left .left-top{
                    margin: 50px 30px 0 30px;
                    padding-bottom: 5px;
                    border-bottom: 1px solid #fff;
                    font-size: 40px;
                    font-weight: bold;
                    color: #fff;
                    user-select: none;
                }
                
                body .left .left-box1{
                    margin: 20px 30px 0 30px;
                    font-size: 14px;
                    color: #fff;
                    user-select: none;
                }
                
                body .left .left-box2{
                    padding-bottom: 5px;
                    border-bottom: 1px solid #fff;
                    margin: 50px 30px 0 30px;
                    font-size: 20px;
                    font-weight: bold;
                    color: #fff;
                    user-select: none;
                }
                
                body .left .left-box3{
                    margin: 20px 30px 0 30px;
                    font-size: 14px;
                    color: #fff;
                    user-select: none;
                }
                
                body .right{
                    position: fixed;
                    left: 28vw;
                    top: 0;
                    overflow: hidden;
                }
                
                svg .handle {
                    fill: #8a8b8e;
                }
                
                svg .selection {
                    fill: #5a5b58;
                    fill-opacity: 1;
                    stroke: none;
                }
                .timeFliter{
                    position: relative;
                    top: 0;
                    bottom: 0;
                    left: 0;
                    right: 0;
                    margin: auto;
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    flex-direction:column;
                }
            </style>
        </head>
        
        <body>
            <div class="left">
                <div class="left-top">文献的可视化探索</div>
                <div class="left-box1">本项目是2022年秋季
                《可视语言与信息可视化（双语）》课程的结课项目，
                项目由智能与计算学部软件工程专业的
                郭以勒、闫瑞泽、范哲昊共同完成。</div>
                <div class="left-box2">成员信息</div>
                <ul class="left-box3">
                    <li>郭以勒 3020209318</li>
                    <li>闫瑞泽 3020001607</li>
                    <li>范哲昊 3020244275</li>
                </ul>
                <div class="left-box2">功能指引</div>
                <ul class="left-box3">
                    <li>实现了散点图的绘制，将每篇文章显示为一个节点</li>
                    <li>实现了一个时间过滤器，当用户进行拖动时可选择一定
                    的时间范围进行文献显示</li>
                    <li>实现了一个透镜，用户点击页面，透镜即出现，透镜将
                    跟随鼠标进行移动，方便用户查看某个区域所包含的关键词
                    （透镜大小可调）</li>
                    <li>实现了可视化交互，用户点击topic，可快速感知各主
                    题在空间中的分布</li>
                    <li>实现了可视化交互，用户点击多个topic，可以快速找
                    到这些跨主题的文章</li>
                </ul>
            </div>
            
            <div class="right">
                <div id="my_dataviz"></div>
                
                <div class="timeFliter">
                    <label>From - To:&nbsp</label>
                    <div id="scale"></div>
                </div>
            </div>
            <script>
                // set the dimensions and margins of the graph
                var margin = {top: 10, right: 30, bottom: 30, left: 60},
                    width = 900 - margin.left - margin.right,
                    height = 900 - margin.top - margin.bottom;
                    
                var flag_len = true;
                var res;
                var allRes = [];
                
                // append the svg object to the body of the page
                var svg = d3.select("#my_dataviz")
                  .append("svg")
                    .attr("width", width + margin.left * 2
                     + margin.right * 3)
                    .attr("height", height - margin.bottom * 6.5);
                    
                svg.append("g")
                  .attr('class', 'grah')
                  .attr('transform', `translate(${margin.left}, 0)`);
                
                //Read the data
                
                // Add X axis
                var x = d3.scaleLinear()
                    .domain([-40, 40])
                    .range([ 0, width - margin.right]);
                /*
                  svg.append("g")
                    .attr("transform", "translate(0," + height + ")")
                    .call(d3.axisBottom(x));*/
                
                
                // Add Y axis
                var y = d3.scaleLinear()
                    .domain([-35, 35])
                    .range([ height - margin.bottom * 6, margin.top * 1.5]);
                /*
                  svg.append("g")
                    .call(d3.axisLeft(y));*/
                    
                d3.csv("../data/data.csv", function(error, data){
                    // console.log(data);
                    
                    // Add dots
                    svg.select('.grah')
                    .append('g')
                    .attr('class', 'dots')
                    .attr('transform', `translate(0, ${-5 * margin.top})`)
                        .selectAll(".showdot")
                        .data(data)
                        .enter()
                        .append("circle")
                          .attr("cx", function (d,i) { return x(d["x"]); } )
                          .attr("cy", function (d,i) { return y(d["y"]); } )
                          .attr("r", 1.5)
                          .style("fill", "#69b3a2")
                          .attr("class", 'showdot')
                          .attr('id', (d, i)=>"circle" + i)
                    var topic = ["topic1","topic2","topic3","topic4"
                    ,"topic5","topic6","topic7","topic8"]
                    
                    // init();
                    
                    // 绘制话题选择区
                    svg.append("g")
                        .attr("class","topicGroup")
                        .attr('transform', `translate(${margin.left * 2}
                        , ${margin.top * 8})`)
                        .selectAll(".topic")
                        .data(topic)
                        .enter()
                        .append("text")
                        .attr("x",width - 70)
                        .attr("y",function(d,i){
                            return 30*i
                        })
                        .attr("class","topic")
                        .text(function(d){
                            return d;
                        })
                        .attr("fill","grey")
                        
                    var selectTopic = [0,0,0,0,0,0,0,0]
                    
                    var highLight = function(){
                        svg.selectAll(".dot")
                        .transition()
                        .duration(1000)
                        .style("fill","#69b3a2")
                        for(var j = 0; j < data.length; ++j){
                            flag = 1
                            flag2 = 0
                            for(var z = 0; z < 8; ++z){
                                if(selectTopic[z] == 1){
                                    flag2 = 1
                                    if(!(data[j]["topic"+(z+1)] >= 0.3)){
                                        flag = 0;
                                        break;
                                    }
                                }
                            }
                            if(flag == 1 && flag2 == 1){
                                svg.select("#circle" + j)
                                    .transition()
                                    .duration(500)
                                    .style("fill","black")
                                    .attr("r",4)
                                    .transition()
                                    .duration(500)
                                    .attr("r",2)
                            }
                            
                        }
                    }
                    
                    svg.selectAll(".topic")
                        .on("click",function(d,i){
                            flag_len = false;
                            selectTopic[i] = (selectTopic[i] + 1) % 2;
                            if(selectTopic[i] == 1){
                                d3.select(this)
                                    .style("fill","black")
                                    .attr('font-weight', 'bold')
                            }
                            else {
                                d3.select(this)
                                    .style("fill","grey")
                                    .attr('font-weight', 500)
                            }
                            highLight();
                            svg.selectAll(".dot")
                                .attr("r",1.5)
                        })
                        
                    // Add time fliter
                    const tx = d3.scaleTime().domain([new Date(1980, 1, 1)
                    , new Date(2024, 1, 1) - 1]).range([0, width]);
                    const txAxis = d3.axisBottom(tx);
                            
                    const brush = d3.brushX()
                            .extent([[0, 0], [width, 5]])
                            .on("brush end", brushed);
                            
                    const context = svg.select('.grah')
                            .append("g")
                            .attr("class", "context")
                            .attr("transform", "translate(" + margin.left
                             + "," + (height-margin.bottom * 8) + ")");
                            
                    context.append("g")
                            .attr("class", "axis axis--tx")
                            .attr("transform", "translate(0," + 2 + ")")
                            .call(txAxis);
                            
                    context.append("g")
                            .attr("class", "brush")
                            .call(brush)
                            .call(brush.move, [0,0]);
                    
                    function brushed() {
                        flag_len = false;
                        
                        const s = d3.event.selection || tx.range();
                        const timeSelection = s.map(tx.invert, tx);
                        const fromYear = +timeSelection[0].getFullYear();
                        const toYear = +timeSelection[1].getFullYear();
                        
                        document.getElementById('scale').innerHTML = 
                        fromYear + ' - ' + toYear;
                        svg.selectAll("circle")
                        .transition()
                        .duration(1000)
                        .style("fill","#69b3a2")
                        .attr('r', 1.5)
                        .attr('opacity', 0)
                        .attr('class', 'dot');
                        
                        for(var j = 0; j < data.length; ++j){
                            if(+data[j]['year'] >= fromYear && +
                            data[j]['year'] <= toYear){
                                res = svg.select("#circle" + j)
                                    .transition()
                                    .duration(500)
                                    .style("fill","black")
                                    .attr("r",4)
                                    .transition()
                                    .duration(500)
                                    .attr("r",2)
                                    .attr('opacity', 1)
                                    .transition()
                                    .duration(1000)
                                    .style("fill","#69b3a2")
                                    .attr("class", 'showdot');
                                    
                                // allRes.push(res);
                            }
                            
                        }
                        
                        // print();
                    }
                    
                    // function print(){
                    // 	console.log(allRes);
                    // }
                    
                    function init(){
                        svg.selectAll("circle")
                        .transition()
                        .duration(1000)
                        .style("fill","#69b3a2")
                        .attr('r', 1.5)
                        .attr('opacity', 1);
                    }
                    
                    
                    // 设置透镜的初始位置
                    var x_len = width / 2;
                    var y_len = height / 2;
                    
                    // 设置透镜的初始比例
                    var scale = 1;
                    
                    // 记录透镜半径
                    var r;
                    
                    // 透镜放大缩小函数
                    let zoom = d3.zoom()
                            .scaleExtent([0.5, 1.6])
                            .on('zoom', function(){
                                zoomed(d3.event.transform);
                            });
                    
                    // 透镜放大缩小函数
                    function zoomed(transform){
                        scale = transform.k;
                        r = d3.select('.len')
                            .attr('r');
                        r = r * scale;
                        // console.log(r * scale);
                        d3.select('.len').attr('transform', 
                        `translate(${x_len}, ${y_len}) scale(${transform.k})`);
                    };
                    
                    // 在svg画布内调用zoom
                    d3.select('svg').call(zoom);
                    
                    var text_g;
                    
                    // 通过点击来调用透镜
                    d3.select('svg').on('click', function(){
                        // console.log('click');
                        
                        if (flag_len){
                            d3.select(this)
                            .append('g')
                            .attr('class', 'len_g')
                            .append('circle')
                            .attr('class', 'len')
                            .attr('fill', 'none')
                            .attr('stroke', '#565656')
                            .attr('stroke-width', 5)
                            .attr('r', 40)
                            .attr('transform', `translate(${width / 2}
                            , ${height / 2})`);
                            
                            text_g = d3.select(this).select('.len_g')
                                    .append('g')
                                    .attr('class', 'text_g');
                            
                            flag_len = !flag_len;
                        }
                        else {
                            d3.select('.len_g').remove();
                            flag_len = !flag_len;
                            reset();
                        }
                    });
                    
                    // 用于存储关键词
                    var words = [];
                    var allwords = [];
                    
                    // 存储并完成关键词分割
                    for (let i = 0; i < data.length; i++){
                        var word = data[i].text;
                        words = word.split(',');
                        // console.log(word);
                        // console.log(words);
                        data[i].words = words;
                        // console.log(data[i].words);
                        allwords.push(words);
                        words = [];
                    }
                    
                    // 计算点是否在透镜内
                    function cal(dx, x, dy, y, r){
                        // console.log('call');
                        // console.log('dx:' + dx + " x:" + x + " dy:"
                         + dy + " y:" + y + " r:" + r);
                        return ((Math.sqrt(Math.pow(dx - x, 2) + 
                        Math.pow(dy - y, 2)) + 3) < r);
                    }
                    
                    // console.log(allwords);
                    
                    // 绘制关键词
                    function drawText(show, dx, dy, dr){
                        var update = text_g.selectAll('.text-place')
                                    .data(show, d => d);
                        
                        var enter = update.enter();
                        var exit = update.exit();
                        
                        enter.append('text')
                                .attr('class', 'text-place')
                                .attr('font-size', 10)
                                .attr('font-weight', 'bold')
                                .attr('fill', '#565656')
                                // .attr('text-anchor', 'middle')
                                .text(d => d)
                                .attr('x', (d, i) => dx + dr * 2 + 
                                10 + Math.floor(i / 10) * 100)
                                .attr('y', (d, i) => dy + i * 20 - 
                                10 - Math.floor(i / 10) * 200);
                                
                        update.text(d => d)
                                .attr('x', (d, i) => dx + dr * 2 + 
                                10 + Math.floor(i / 10) * 100)
                                .attr('y', (d, i) => dy + i * 20 - 
                                10 - Math.floor(i / 10) * 200);
                                
                        exit.remove();
                    }
                    
                    function reset(){
                        d3.selectAll('.showdot')
                        .style('fill', '#69b3a2')
                        .attr("r", 1.5);
                        
                        d3.selectAll('.dot')
                        .style('fill', '#69b3a2')
                        .attr("r", 1.5);
                    };
                    
                    console.log(d3.selectAll('.dot'));
                    
                    // 设置交互，使透镜跟随鼠标位置移动
                    d3.select('svg')
                    .on('mousemove', function(d){
                        x_len = d3.mouse(this)[0];
                        y_len = d3.mouse(this)[1];
                        
                        // 初始恢复所有点为未选中状态
                        if (!flag_len){
                            if (y_len < 615)
                            reset();
                        }
                        
                        // console.log('x:' + x_len);
                        // console.log('y:' + y_len);
                        
                        // 修改透镜的位置
                        d3.select(this)
                        .select('.len')
                        .attr('transform', `translate(${x_len}, 
                        ${y_len}) scale(${scale})`);
                        
                        // 获取透镜半径
                        r = d3.select(this)
                            .select('.len')
                            .attr('r');
                        
                        r = r * scale;
                        
                        // console.log(r);
                        
                        // 获取在透镜范围内的点
                        var value = d3.selectAll('.showdot')
                                .filter(d => {
                                    if (x(d.x) < x_len - margin.left
                                     - r || x(d.x) > x_len - margin.left
                                      + r || y(d.y) < y_len + 
                                      margin.top * 5 - r || y(d.y) > y_len
                                       + margin.top * 5 + r)
                                        return false;
                                    else
                                        return cal(x(d.x), x_len - 
                                        margin.left, y(d.y), y_len + 
                                        margin.top * 5, r);
                                })
                                .style('fill', 'black')
                                .attr("r", 2)
                                .data();
                        // var value = find(quadtree, x, y, r);
                        
                        // console.log(value.length);
                        // console.log(value);
                        
                        var map = new Map();
                        var show = [];
                        // d3.select(this)
                        // .select('.len_g')
                        
                        // 筛选关键词
                        for (let i = 0; i < value.length; i++){
                            for (let j = 0; j < value[i].words.length; j++){
                                if (map.has(value[i].words[j])){
                                    var cnt = map.get(value[i].words[j]);
                                    cnt++;
                                    map.set(value[i].words[j], cnt);
                                }
                                else {
                                    map.set(value[i].words[j], 1);
                                }
                            }
                        }
                        
                        var arrayObj = Array.from(map);
                        // console.log(arrayObj);
                        arrayObj.sort(function(a,b){return b[1] - a[1]});
                        let cccc = 0;
                        
                        for (var [key, value] of arrayObj) {
                            cccc++;
                            if (cccc > arrayObj.length / 70){
                                break;
                            }
                            // console.log(key + ' = ' + value);
                            show.push(key);
                        }
                        
                        // for (let [key, value] of map.entries()){
                        // 	// console.log(value);
                            
                        // 	if (value >= 20) {
                        // 		// console.log(key);
                        // 		show.push(key);
                        // 	}
                        // }
                        
                        // // console.log(show);
                        drawText(show, x_len - margin.left, y_len + 
                        margin.top * 5, r);
                    });
                    
                })
            </script>
        </body>
    </html>
\end{lstlisting}

\newpage


\bibliography{ref}

\end{document}

